﻿using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Colors;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WCAD.Kean
{
    // This class encapsulates pen

    // information and will be

    // used by our TurtleEngine


    class Pen

    {

        // Private members


        private Color m_color;

        private double m_width;

        private bool m_down;


        // Public properties


        public Color Color

        {

            get { return m_color; }

            set { m_color = value; }

        }


        public double Width

        {

            get { return m_width; }

            set { m_width = value; }

        }


        public bool Down

        {

            get { return m_down; }

            set { m_down = value; }

        }


        // Constructor


        public Pen()

        {

            m_color =

              Color.FromColorIndex(ColorMethod.ByAci, 0);

            m_width = 0.0;

            m_down = false;

        }

    }


    // The main Turtle Graphics engine


    class TurtleEngine

    {

        // Private members


        private Transaction m_trans;

        private Polyline m_poly;

        private Pen m_pen;

        private Point3d m_position;

        private Vector3d m_direction;

        private bool m_updateGraphics;


        // Public properties


        public Point3d Position

        {

            get { return m_position; }

            set { m_position = value; }

        }


        public Vector3d Direction

        {

            get { return m_direction; }

            set { m_direction = value; }

        }


        // Constructor


        public TurtleEngine(Transaction tr)

        {

            m_pen = new Pen();

            m_trans = tr;

            m_poly = null;

            m_position = Point3d.Origin;

            m_direction = new Vector3d(1.0, 0.0, 0.0);

            m_updateGraphics = false;

        }


        // Public methods


        public void Turn(double angle)

        {

            // Rotate our direction by the

            // specified angle


            Matrix3d mat =

              Matrix3d.Rotation(

                angle,

                Vector3d.ZAxis,

                Position

              );


            Direction =

              Direction.TransformBy(mat);

        }


        public void Move(double distance)

        {

            // Move the cursor by a specified

            // distance in the direction in

            // which we're pointing


            Point3d oldPos = Position;

            Position += Direction * distance;


            // If the pen is down, we draw something


            if (m_pen.Down)

                GenerateSegment(oldPos, Position);

        }


        public void PenDown()

        {

            m_pen.Down = true;

        }


        public void PenUp()

        {

            m_pen.Down = false;


            // We'll start a new entity with the next

            // use of the pen


            m_poly = null;

        }


        public void SetPenWidth(double width)

        {

            m_pen.Width = width;

        }


        public void SetPenColor(int idx)

        {

            // Right now we just use an ACI,

            // to make the code simpler


            Color col =

              Color.FromColorIndex(

                ColorMethod.ByAci,

                (short)idx

              );


            // If we have to change the color,

            // we'll start a new entity

            // (if the entity type we're creating

            // supports per-segment colors, we

            // don't need to do this)


            if (col != m_pen.Color)

            {

                m_poly = null;

                m_pen.Color = col;

            }

        }


        // Internal helper to generate geometry

        // (this could be optimised to keep the

        // object we're generating open, rather

        // than having to reopen it each time)


        private void GenerateSegment(

          Point3d oldPos, Point3d newPos)

        {

            Document doc =

              Application.DocumentManager.MdiActiveDocument;

            Database db = doc.Database;

            Editor ed = doc.Editor;


            Autodesk.AutoCAD.ApplicationServices.

            TransactionManager tm =

              doc.TransactionManager;


            Plane plane;


            // Create the current object, if there is none


            if (m_poly == null)

            {

                BlockTable bt =

                  (BlockTable)m_trans.GetObject(

                    db.BlockTableId,

                    OpenMode.ForRead

                  );

                BlockTableRecord ms =

                  (BlockTableRecord)m_trans.GetObject(

                    bt[BlockTableRecord.ModelSpace],

                    OpenMode.ForWrite

                  );


                // Create the polyline


                m_poly = new Polyline();

                m_poly.Color = m_pen.Color;


                // Define its plane


                plane = new Plane(

                  m_poly.Ecs.CoordinateSystem3d.Origin,

                  m_poly.Ecs.CoordinateSystem3d.Zaxis

                );


                // Add the first vertex


                m_poly.AddVertexAt(

                  0, oldPos.Convert2d(plane),

                  0.0, m_pen.Width, m_pen.Width

                );


                // Add the polyline to the database


                ms.AppendEntity(m_poly);

                m_trans.AddNewlyCreatedDBObject(m_poly, true);

            }

            else

            {

                // Calculate its plane


                plane = new Plane(

                  m_poly.Ecs.CoordinateSystem3d.Origin,

                  m_poly.Ecs.CoordinateSystem3d.Zaxis

                );

            }


            // Make sure the previous vertex has its

            // width set appropriately


            if (m_pen.Width > 0.0)

            {

                m_poly.SetStartWidthAt(

                  m_poly.NumberOfVertices - 1,

                  m_pen.Width

                );

                m_poly.SetEndWidthAt(

                  m_poly.NumberOfVertices - 1,

                  m_pen.Width

                );

            }


            // Add the new vertex


            m_poly.AddVertexAt(

              m_poly.NumberOfVertices,

              newPos.Convert2d(plane),

              0.0, m_pen.Width, m_pen.Width

            );


            // Display the graphics, to avoid long,

            // black-box operations


            if (m_updateGraphics)

            {

                tm.QueueForGraphicsFlush();

                tm.FlushGraphics();

                ed.UpdateScreen();

            }

        }

    }


    public class Commands

    {

        static void Fern(

          TurtleEngine te,

          double distance

        )

        {

            const double minDist = 0.3;

            const double widthFactor = 0.1;

            const double stemFactor = 0.04;

            const double restFactor = 0.85;

            const double branchFactor = 0.3;

            const int stemSegs = 5;

            const int stemSegAngle = 1;


            if (distance < minDist)

                return;


            // Width of the trunk/branch is a fraction

            // of the length


            te.SetPenWidth(

              distance * stemFactor * widthFactor

            );


            // Draw the stem


            for (int i = 0; i < stemSegs; i++)

            {

                te.Move(distance * stemFactor / stemSegs);

                if (i < stemSegs - 1)

                    te.Turn(-stemSegAngle * Math.PI / 180);

            }


            // Draw the left-hand sub-fern


            te.Turn(Math.PI / 2);

            Fern(te, distance * branchFactor);


            // Draw the right-hand sub-fern


            te.Turn(-Math.PI);

            Fern(te, distance * branchFactor);


            // Draw the rest of the fern to the front


            te.Turn(Math.PI / 2);

            Fern(te, distance * restFactor);


            // Draw back down to the start of this sub-

            // fern, with the same thickness, as this

            // may have changed in deeper sub-ferns


            te.SetPenWidth(

              distance * stemFactor * widthFactor

            );


            for (int i = 0; i < stemSegs; i++)

            {

                te.Move(-distance * stemFactor / stemSegs);

                if (i < stemSegs - 1)

                    te.Turn(stemSegAngle * Math.PI / 180);

            }

        }


        static public bool GetFernInfo(

          out Point3d position,

          out double treeLength

        )

        {

            Document doc =

              Application.DocumentManager.MdiActiveDocument;

            Editor ed = doc.Editor;


            treeLength = 0;

            position = Point3d.Origin;


            PromptPointOptions ppo =

              new PromptPointOptions(

                "\nSelect base point of fern: "

              );


            PromptPointResult ppr =

              ed.GetPoint(ppo);


            if (ppr.Status != PromptStatus.OK)

                return false;


            position = ppr.Value;


            PromptDoubleOptions pdo =

              new PromptDoubleOptions(

                "\nEnter fern length <100>: "

              );


            pdo.AllowNone = true;


            PromptDoubleResult pdr =

              ed.GetDouble(pdo);


            if (pdr.Status != PromptStatus.None &&

                pdr.Status != PromptStatus.OK)

                return false;


            if (pdr.Status == PromptStatus.OK)

                treeLength = pdr.Value;

            else

                treeLength = 100;


            return true;

        }


        [CommandMethod("FF")]

        static public void FractalFern()

        {

            Document doc =

              Application.DocumentManager.MdiActiveDocument;


            double fernLength;

            Point3d position;


            if (!GetFernInfo(out position, out fernLength))

                return;


            Transaction tr =

              doc.TransactionManager.StartTransaction();

            using (tr)

            {

                TurtleEngine te = new TurtleEngine(tr);


                // Draw a fractal fern


                te.Position = position;

                te.SetPenColor(92);

                te.SetPenWidth(0);

                te.Turn(Math.PI / 2);

                te.PenDown();


                Fern(te, fernLength);


                tr.Commit();

            }

        }

    }


    //// This class encapsulates pen

    //// information and will be

    //// used by our TurtleEngine


    //class Pen

    //{

    //    // Private members


    //    private Color m_color;

    //    private double m_width;

    //    private bool m_down;


    //    // Public properties


    //    public Color Color

    //    {

    //        get { return m_color; }

    //        set { m_color = value; }

    //    }


    //    public double Width

    //    {

    //        get { return m_width; }

    //        set { m_width = value; }

    //    }


    //    public bool Down

    //    {

    //        get { return m_down; }

    //        set { m_down = value; }

    //    }


    //    // Constructor


    //    public Pen()

    //    {

    //        m_color =

    //          Color.FromColorIndex(ColorMethod.ByAci, 0);

    //        m_width = 0.0;

    //        m_down = false;

    //    }

    //}


    //// The main Turtle Graphics engine


    //class TurtleEngine : IDisposable

    //{

    //    // Private members


    //    private Transaction m_trans;

    //    private Polyline3d m_poly;

    //    private Circle m_profile;

    //    private Pen m_pen;

    //    private CoordinateSystem3d m_ecs;

    //    private bool m_updateGraphics;


    //    // Public properties


    //    public Point3d Position

    //    {

    //        get { return m_ecs.Origin; }

    //        set
    //        {

    //            m_ecs =

    //              new CoordinateSystem3d(

    //                value,

    //                m_ecs.Xaxis,

    //                m_ecs.Yaxis

    //              );

    //        }

    //    }


    //    public Vector3d Direction

    //    {

    //        get { return m_ecs.Xaxis; }

    //    }


    //    // Constructor


    //    public TurtleEngine(Transaction tr)

    //    {

    //        m_pen = new Pen();

    //        m_trans = tr;

    //        m_poly = null;

    //        m_profile = null;

    //        m_ecs =

    //          new CoordinateSystem3d(

    //            Point3d.Origin,

    //            Vector3d.XAxis,

    //            Vector3d.YAxis

    //          );

    //        m_updateGraphics = false;

    //    }


    //    public void Dispose()

    //    {

    //        TerminateCurrentSection();

    //    }


    //    // Public methods


    //    public void Turn(double angle)

    //    {

    //        // Rotate our direction by the

    //        // specified angle


    //        Matrix3d mat =

    //          Matrix3d.Rotation(

    //            angle,

    //            m_ecs.Zaxis,

    //            Position

    //          );


    //        m_ecs =

    //          new CoordinateSystem3d(

    //            m_ecs.Origin,

    //            m_ecs.Xaxis.TransformBy(mat),

    //            m_ecs.Yaxis.TransformBy(mat)

    //          );

    //    }


    //    public void Pitch(double angle)

    //    {

    //        // Pitch in our direction by the

    //        // specified angle


    //        Matrix3d mat =

    //          Matrix3d.Rotation(

    //            angle,

    //            m_ecs.Yaxis,

    //            m_ecs.Origin

    //          );


    //        m_ecs =

    //          new CoordinateSystem3d(

    //            m_ecs.Origin,

    //            m_ecs.Xaxis.TransformBy(mat),

    //            m_ecs.Yaxis

    //          );

    //    }


    //    public void Roll(double angle)

    //    {

    //        // Roll along our direction by the

    //        // specified angle


    //        Matrix3d mat =

    //          Matrix3d.Rotation(

    //            angle,

    //            m_ecs.Xaxis,

    //            m_ecs.Origin

    //          );


    //        m_ecs =

    //          new CoordinateSystem3d(

    //            m_ecs.Origin,

    //            m_ecs.Xaxis,

    //            m_ecs.Yaxis.TransformBy(mat)

    //          );

    //    }


    //    public void Move(double distance)

    //    {

    //        // Move the cursor by a specified

    //        // distance in the direction in

    //        // which we're pointing


    //        Point3d oldPos = m_ecs.Origin;

    //        Point3d newPos = oldPos + m_ecs.Xaxis * distance;


    //        m_ecs =

    //          new CoordinateSystem3d(

    //            newPos,

    //            m_ecs.Xaxis,

    //            m_ecs.Yaxis

    //          );


    //        // If the pen is down, we draw something


    //        if (m_pen.Down)

    //            GenerateSegment(oldPos, newPos);

    //    }


    //    public void PenDown()

    //    {

    //        m_pen.Down = true;

    //    }


    //    public void PenUp()

    //    {

    //        m_pen.Down = false;


    //        // We'll start a new entity with the next

    //        // use of the pen


    //        TerminateCurrentSection();

    //    }


    //    public void SetPenWidth(double width)

    //    {

    //        m_pen.Width = width;

    //        TerminateCurrentSection();

    //    }


    //    public void SetPenColor(int idx)

    //    {

    //        // Right now we just use an ACI,

    //        // to make the code simpler


    //        Color col =

    //          Color.FromColorIndex(

    //            ColorMethod.ByAci,

    //            (short)idx

    //          );


    //        // If we have to change the color,

    //        // we'll start a new entity

    //        // (if the entity type we're creating

    //        // supports per-segment colors, we

    //        // don't need to do this)


    //        if (col != m_pen.Color)

    //        {

    //            TerminateCurrentSection();

    //            m_pen.Color = col;

    //        }

    //    }


    //    // Internal helper to generate geometry


    //    private void GenerateSegment(

    //      Point3d oldPos, Point3d newPos)

    //    {

    //        Document doc =

    //          Application.DocumentManager.MdiActiveDocument;

    //        Database db = doc.Database;

    //        Editor ed = doc.Editor;


    //        Autodesk.AutoCAD.ApplicationServices.

    //        TransactionManager tm =

    //          doc.TransactionManager;


    //        // Create the current object, if there is none


    //        if (m_poly == null)

    //        {

    //            BlockTable bt =

    //              (BlockTable)m_trans.GetObject(

    //                db.BlockTableId,

    //                OpenMode.ForRead

    //              );

    //            BlockTableRecord ms =

    //              (BlockTableRecord)m_trans.GetObject(

    //                bt[BlockTableRecord.ModelSpace],

    //                OpenMode.ForWrite

    //              );


    //            // Create the polyline


    //            m_poly = new Polyline3d();

    //            m_poly.Color = m_pen.Color;


    //            // Add the polyline to the database


    //            ms.AppendEntity(m_poly);

    //            m_trans.AddNewlyCreatedDBObject(m_poly, true);


    //            // Add the first vertex


    //            PolylineVertex3d vert =

    //              new PolylineVertex3d(oldPos);


    //            m_poly.AppendVertex(vert);

    //            m_trans.AddNewlyCreatedDBObject(vert, true);


    //            m_profile =

    //              new Circle(oldPos, Direction, m_pen.Width);

    //            ms.AppendEntity(m_profile);

    //            m_trans.AddNewlyCreatedDBObject(m_profile, true);

    //            m_profile.DowngradeOpen();

    //        }


    //        // Add the new vertex


    //        PolylineVertex3d vert2 =

    //          new PolylineVertex3d(newPos);


    //        m_poly.AppendVertex(vert2);

    //        m_trans.AddNewlyCreatedDBObject(vert2, true);


    //        // Display the graphics, to avoid long,

    //        // black-box operations


    //        if (m_updateGraphics)

    //        {

    //            tm.QueueForGraphicsFlush();

    //            tm.FlushGraphics();

    //            ed.UpdateScreen();

    //        }

    //    }


    //    // Internal helper to generate 3D geometry


    //    private void TerminateCurrentSection()

    //    {

    //        if (m_profile != null && m_poly != null)

    //        {

    //            Document doc =

    //              Application.DocumentManager.MdiActiveDocument;

    //            Database db = doc.Database;

    //            Editor ed = doc.Editor;


    //            try

    //            {

    //                // Generate a Region from our circular profile


    //                DBObjectCollection col =

    //                  new DBObjectCollection();

    //                col.Add(m_profile);


    //                DBObjectCollection res =

    //                  Region.CreateFromCurves(col);


    //                Region reg =

    //                  res[0] as Region;

    //                if (reg != null)

    //                {

    //                    BlockTable bt =

    //                      (BlockTable)m_trans.GetObject(

    //                        db.BlockTableId,

    //                        OpenMode.ForRead

    //                      );

    //                    BlockTableRecord ms =

    //                      (BlockTableRecord)m_trans.GetObject(

    //                        bt[BlockTableRecord.ModelSpace],

    //                        OpenMode.ForWrite

    //                      );


    //                    // Extrude our Region along the Polyline3d path


    //                    Solid3d sol = new Solid3d();

    //                    sol.ExtrudeAlongPath(reg, m_poly, 0.0);

    //                    sol.Color = m_pen.Color;


    //                    // Add the generated Solid3d to the database


    //                    ms.AppendEntity(sol);

    //                    m_trans.AddNewlyCreatedDBObject(sol, true);


    //                    // Get rid of the Region, profile and path


    //                    reg.Dispose();

    //                    m_profile.UpgradeOpen();

    //                    m_profile.Erase();

    //                    m_poly.Erase();

    //                }

    //            }

    //            catch (System.Exception ex)

    //            {

    //                ed.WriteMessage(

    //                  "\nException: {0}",

    //                  ex.Message

    //                );

    //            }

    //        }

    //        m_profile = null;

    //        m_poly = null;

    //    }

    //}


    //public class Commands

    //{

    //    static public bool GetTreeInfo(

    //      out Point3d position,

    //      out double treeLength,

    //      out int variability

    //    )

    //    {

    //        Document doc =

    //          Application.DocumentManager.MdiActiveDocument;

    //        Editor ed = doc.Editor;


    //        treeLength = 0;

    //        position = Point3d.Origin;

    //        variability = 0;


    //        PromptPointOptions ppo =

    //          new PromptPointOptions(

    //            "\nSelect base point of tree: "

    //          );


    //        PromptPointResult ppr =

    //          ed.GetPoint(ppo);


    //        if (ppr.Status != PromptStatus.OK)

    //            return false;


    //        position = ppr.Value;


    //        PromptDoubleOptions pdo =

    //          new PromptDoubleOptions(

    //            "\nEnter tree length <100>: "

    //          );


    //        pdo.AllowNone = true;


    //        PromptDoubleResult pdr =

    //          ed.GetDouble(pdo);


    //        if (pdr.Status != PromptStatus.None &&

    //            pdr.Status != PromptStatus.OK)

    //            return false;


    //        if (pdr.Status == PromptStatus.OK)

    //            treeLength = pdr.Value;

    //        else

    //            treeLength = 100;


    //        PromptIntegerOptions pio =

    //          new PromptIntegerOptions(

    //            "\nEnter variability percentage <20>: "

    //          );


    //        pio.AllowNone = true;


    //        PromptIntegerResult pir =

    //          ed.GetInteger(pio);


    //        if (pir.Status != PromptStatus.None &&

    //            pir.Status != PromptStatus.OK)

    //            return false;


    //        if (pir.Status == PromptStatus.OK)

    //            variability = pir.Value;

    //        else

    //            variability = 20;


    //        return true;

    //    }


    //    static void Random3DTree(

    //      TurtleEngine te,

    //      double distance,

    //      int variability

    //    )

    //    {

    //        // Some constants


    //        const double kLeafSize = 3.0;

    //        const double kBasePitch = Math.PI / 4;

    //        const double kBaseRoll = 2 * Math.PI / 3;

    //        const double kBranchFactor = 0.6;

    //        const double kBranchWidth = 0.05;

    //        const int kWoodColor = 24;

    //        const int kLeafColor = 3;


    //        // Under a certain size, we draw a leaf


    //        if (distance < kLeafSize)

    //        {

    //            // Draw a leaf-like cylinder


    //            te.SetPenColor(kLeafColor);

    //            te.PenDown();

    //            te.Move(kLeafSize);

    //            te.PenUp();

    //            te.Move(-kLeafSize);


    //            return;

    //        }


    //        // Generate 7 random factors, each on the same basis:

    //        //  a base amount = 100 - half the variability

    //        //  + a random amount from 0 to the variability

    //        // So a variability of 20 results in 90 to 110 (0.9-1.1)


    //        Random rnd = new Random();

    //        int basic = 100 - (variability / 2);

    //        int num = rnd.Next(variability);

    //        double factor1 = (basic + num) / 100.0;

    //        num = rnd.Next(variability);

    //        double factor2 = (basic + num) / 100.0;

    //        num = rnd.Next(variability);

    //        double factor3 = (basic + num) / 100.0;

    //        num = rnd.Next(variability);

    //        double factor4 = (basic + num) / 100.0;

    //        num = rnd.Next(variability);

    //        double factor5 = (basic + num) / 100.0;

    //        num = rnd.Next(variability);

    //        double factor6 = (basic + num) / 100.0;

    //        num = rnd.Next(variability);

    //        double factor7 = (basic + num) / 100.0;


    //        // Multiple out the various items by the factors


    //        double move1 = factor1 * distance;

    //        double pitch1 = factor2 * kBasePitch;

    //        double pitch2 = factor3 * kBasePitch;

    //        double pitch3 = factor4 * kBasePitch;

    //        double roll1 = factor5 * 2 * Math.PI;

    //        double roll2 = roll1 + (factor6 * kBaseRoll);

    //        double roll3 = roll1 + roll2 + (factor7 * kBaseRoll);


    //        // Width of the trunk/branch is a tenth of

    //        // of the length


    //        te.SetPenWidth(move1 * kBranchWidth);

    //        te.SetPenColor(kWoodColor);

    //        te.PenDown();


    //        // Draw the main trunk/branch


    //        te.Move(move1);


    //        // Draw the first sub-tree


    //        te.Roll(roll1);

    //        te.Pitch(pitch1);

    //        Random3DTree(te, distance * kBranchFactor, variability);

    //        te.Pitch(-pitch1);

    //        te.Roll(-roll1);


    //        // Draw the second sub-tree


    //        te.Roll(roll2);

    //        te.Pitch(pitch2);

    //        Random3DTree(te, distance * kBranchFactor, variability);

    //        te.Pitch(-pitch2);

    //        te.Roll(-roll2);


    //        // Draw the third sub-tree


    //        te.Roll(roll3);

    //        te.Pitch(pitch3);

    //        Random3DTree(te, distance * kBranchFactor, variability);

    //        te.Pitch(-pitch3);

    //        te.Roll(-roll3);


    //        // Draw back down to the start of this sub-

    //        // tree, with the same thickness, as this

    //        // may have changed in deeper sub-trees


    //        te.PenUp();

    //        te.Move(-move1);

    //    }


    //    [CommandMethod("3DT")]

    //    static public void ThreeDimensionalTree()

    //    {

    //        Document doc =

    //          Application.DocumentManager.MdiActiveDocument;

    //        Editor ed = doc.Editor;


    //        double treeLength;

    //        Point3d position;

    //        int variability;


    //        if (!GetTreeInfo(

    //              out position,

    //              out treeLength,

    //              out variability

    //              )

    //            )

    //            return;


    //        Transaction tr =

    //          doc.TransactionManager.StartTransaction();

    //        using (tr)

    //        {

    //            TurtleEngine te = new TurtleEngine(tr);

    //            using (te)

    //            {

    //                // Draw a random fractal tree


    //                te.Position = position;

    //                te.Pitch(Math.PI / -2);


    //                Random3DTree(te, treeLength, variability);

    //            }

    //            tr.Commit();

    //        }

    //    }

    //}
}
